#include "head.cuh"


void read_from_file(int *B, const int r, const int n, const char *filename){
	FILE *f;
	f = fopen(filename, "r");
	if (f == NULL){
		printf("read %s failed!\n", filename);
		system("pause");
		exit(1);
	}
	fseek(f, 0, SEEK_SET);

	char *buffer = (char*)malloc((r + 1)*sizeof(char));
	int cnt = 0;
	while (EOF != fscanf(f, "%s\n", buffer)){
		for (int i = 0; i < r; i++){
			B[i + cnt*r] = buffer[i] - '0';
		}
		cnt++;
	}
	fclose(f);

}
void read_test_data(float *UIview_test, const int m, const int n,
	const char *filename){
	for (int i = 0; i < m*n; i++) UIview_test[i] = 0.0f;

	int row = 0;
	int col = 0;
	float val = 0.0;
	FILE *f;
	f = fopen(filename, "r");
	if (f == NULL){
		printf("read %s failed!\n", filename);
		system("pause");
		exit(1);
	}
	fseek(f, 0, SEEK_SET);
	while (EOF != fscanf(f, "%u%u%f\n", &row, &col, &val)){
		UIview_test[row + col*m] = val;
	}

	fclose(f);
}

__global__ void get_d_cosine_distance_kernel(float *d_cosine_distance, const float *d_B, const float *d_D,
	const int r, const int m, const int n, const float *d_UIview_test){
	int row = threadIdx.y + blockDim.y * blockIdx.y;
	int col = threadIdx.x + blockDim.x * blockIdx.x;
	if (row < n&&col < m){
		if (d_UIview_test[col + row*m] <= 1e-7){
			d_cosine_distance[row + col*n] = 3;
		}
		else
		{
			float t1 = 0.0f;
			float t2 = 0.0f;
			float t3 = 0.0f;
			for (int i = 0; i < r; i++){
				t1 += d_B[i + col*r] * d_D[i + row*r];
				t2 += d_B[i + col*r] * d_B[i + col*r];
				t3 += d_D[i + row*r] * d_D[i + row*r];
			}
			d_cosine_distance[row + col*n] = 1 - t1 / (sqrtf(t2)*sqrtf(t3));
		}
	}
}


__global__ void get_hamming_distance_kernel(int *d_hamming_distance, const int *d_B, const int *d_D,
	const int r, const int m, const int n, const float *d_UIview_test){
	int row = threadIdx.y + blockDim.y * blockIdx.y;
	int col = threadIdx.x + blockDim.x * blockIdx.x;
	if (row < n&&col < m){
		if (d_UIview_test[col + row*m] <= 1e-7){
			d_hamming_distance[row + col*n] = r + 1;
			// for no-rating user-item pairs their distance should be very big.
			// in this case r+1 is enough.
		}
		else
		{
			int cnt = r;
			for (int i = 0; i < r; i++){
				if (d_B[i + col*r] == d_D[i + row*r])
					cnt--;
			}
			d_hamming_distance[row + col*n] = cnt;
		}
	}
}

void get_hamming_distance(int *d_hamming_distance, /* n X m */
	const int *d_B, const int *d_D,
	const int r, const int m, const int n, const float *d_UIview_test){
	dim3 szBlock;
	if (m > 2 * n){
		szBlock.x = 64;
		szBlock.y = 16;
	}
	else if (n > 2 * m){
		szBlock.x = 16;
		szBlock.y = 64;
	}
	else{
		szBlock.x = 32;
		szBlock.y = 32;
	}
	szBlock.z = 1;
	dim3 szGrid((m + szBlock.x - 1) / szBlock.x, (n + szBlock.y - 1) / szBlock.y, 1);
	get_hamming_distance_kernel << <szGrid, szBlock >> >(d_hamming_distance,
		d_B, d_D, r, m, n, d_UIview_test);
	cudaDeviceSynchronize();
	CHECK_CUDA(cudaGetLastError());
}

void get_min_k_predict(int *d_predict, const int k, const int m,
	const int *d_hamming_distance, const int n){
	int *hamming_distance = (int *)malloc(n*m*sizeof(int));
	CHECK_CUDA(cudaMemcpy(hamming_distance, d_hamming_distance, n*m*sizeof(int),
		cudaMemcpyDeviceToHost));
	int *predict = (int*)malloc(k*m*sizeof(int));
	Ele *tmp;
	tmp = (Ele*)malloc(n*sizeof(Ele));
	for (int i = 0; i < m; i++){
		for (int j = 0; j < n; j++){
			tmp[j].val = hamming_distance[j + i*n];
			tmp[j].pos = j;
		}
		std::sort(tmp, tmp + n);
		for (int j = 0; j < k; j++){
			predict[j + i*k] = tmp[j].pos;
		}
	}
	CHECK_CUDA(cudaMemcpy(d_predict, predict, k*m*sizeof(int), cudaMemcpyHostToDevice));
	free(hamming_distance);
	free(tmp);
	free(predict);
}

__global__ void get_measure_kernel(float *d_measure, const int *d_predict,
	const int k,
	const int m,
	const int n,
	const float *d_UIview_test){
	int tid = threadIdx.x + blockIdx.x * blockDim.x;
	if (tid < m){
		for (int i = 0; i < k; i++){
			d_measure[i + tid*k] = d_UIview_test[tid + m*d_predict[i + tid*k]];
		}
	}
}

void get_measure(float *d_measure, const int *d_predict,
	const int k,
	const int m,
	const int n,
	const float *d_UIview_test){
	int threadsPerBlock = 512;
	int blocksPerGrid = (m + threadsPerBlock - 1) / threadsPerBlock;
	get_measure_kernel << <blocksPerGrid, threadsPerBlock >> >(
		d_measure, d_predict, k, m, n, d_UIview_test);
	cudaDeviceSynchronize();
	CHECK_CUDA(cudaGetLastError());
}
__global__ void get_score_kernel(float *d_score,
	const float *d_measure, const int k, const int m, const float threshold){
	int tid = threadIdx.x + blockIdx.x*blockDim.x;
	if (tid < m){
		int total = 0;
		for (int i = 0; i < k; i++){
			if (d_measure[i + tid*k] >= threshold){
				total += 1;
				d_score[tid] += 1.0f*total / (i + 1);
			}
		}
		if (total != 0){
			d_score[tid] = d_score[tid] / total;
		}
	}
}
float get_map(const float *d_measure,
	const int k,
	const int m,
	float threshold,
	cublasHandle_t cublashandle){
	float map_score;

	float *d_score;
	CHECK_CUDA(cudaMalloc(&d_score, m*sizeof(float)));
	CHECK_CUDA(cudaMemset(d_score, 0, m*sizeof(float)));
	int threadsPerBlock = 512;
	int blocksPerGrid = (m + threadsPerBlock - 1) / threadsPerBlock;
	get_score_kernel << <blocksPerGrid, threadsPerBlock >> >(d_score,
		d_measure, k, m, threshold);
	CHECK_CUDA_STATUS(cublasSasum(cublashandle, m, d_score, 1, &map_score));
	CHECK_CUDA(cudaFree(d_score));
	map_score /= m;
	return map_score;
}
__global__ void get_ndcg_kernel(const float *d_measure,
	const int k, const int m,
	float *d_tmp){
	int tid = threadIdx.x + blockIdx.x*blockDim.x;
	if (tid < m){
		float temp = 0;
		for (int i = 0; i < k; i++){
			temp += (powf(2.0f, d_measure[i + tid*k]) - 1) / log2f(i + 2);
		}
		d_tmp[tid] = temp;
	}
}
float get_ndcg(const float *d_measure, const int k, const int m,
	const float *UIview_test, const int n){
	float *d_tmp;
	CHECK_CUDA(cudaMalloc(&d_tmp, m*sizeof(float)));
	int threadsPerBlock = 512;
	int blocksPerGrid = (m + threadsPerBlock - 1) / threadsPerBlock;
	get_ndcg_kernel << <blocksPerGrid, threadsPerBlock >> >(d_measure,
		k, m, d_tmp);
	float *tmp = (float*)malloc(m*sizeof(float));
	CHECK_CUDA(cudaMemcpy(tmp, d_tmp, m*sizeof(float), cudaMemcpyDeviceToHost));
	CHECK_CUDA(cudaFree(d_tmp));

	Ele *ele;
	ele = (Ele*)malloc(n*sizeof(Ele));

	float *idealpre = (float*)malloc(k*m*sizeof(float));
	for (int i = 0; i < m; i++){
		for (int j = 0; j < n; j++){
			ele[j].val = -1.0f*UIview_test[i + j*m];
			ele[j].pos = j;
		}
		std::sort(ele, ele + n);
		for (int j = 0; j < k; j++){
			idealpre[j + i*k] = -1.0f * ele[j].val;
			//printf("%.2f ",idealpre[j+i*k]);
		}
		//printf("\n");
		//system("pause");
	}
	free(ele);
	float *d_idealpre;
	CHECK_CUDA(cudaMalloc(&d_idealpre, k*m*sizeof(float)));
	CHECK_CUDA(cudaMemcpy(d_idealpre, idealpre, k*m*sizeof(float),
		cudaMemcpyHostToDevice));
	free(idealpre);
	float *d_tmp2;
	CHECK_CUDA(cudaMalloc(&d_tmp2, m*sizeof(float)));
	get_ndcg_kernel << <blocksPerGrid, threadsPerBlock >> >(d_idealpre,
		k, m, d_tmp2);
	float *tmp2 = (float*)malloc(m*sizeof(float));
	CHECK_CUDA(cudaMemcpy(tmp2, d_tmp2, m*sizeof(float), cudaMemcpyDeviceToHost));
	CHECK_CUDA(cudaFree(d_tmp2));
	CHECK_CUDA(cudaFree(d_idealpre));

	float ndcg_score = 0.0f;
	for (int i = 0; i < m; i++){
		if (tmp2[i]>0)
			ndcg_score += tmp[i] / tmp2[i];
		else
			ndcg_score += tmp[i];
	}
	ndcg_score /= m;
	free(tmp);
	free(tmp2);
	return ndcg_score;
}


void evaluate(const int *B,/* r X m {0,1}*/
	const int *D, /* r X n */
	const int r,
	const int m,
	const int n,
	const float *UIview_test,/* m X n [0.0, 5.0]*/
	const int result_num,
	const float threshold){

	int *d_B;
	CHECK_CUDA(cudaMalloc(&d_B, r*m*sizeof(int)));
	CHECK_CUDA(cudaMemcpy(d_B, B, r*m*sizeof(int), cudaMemcpyHostToDevice));
	int *d_D;
	CHECK_CUDA(cudaMalloc(&d_D, r*n*sizeof(int)));
	CHECK_CUDA(cudaMemcpy(d_D, D, r*n*sizeof(int), cudaMemcpyHostToDevice));
	float *d_UIview_test;
	CHECK_CUDA(cudaMalloc(&d_UIview_test, m*n*sizeof(float)));
	CHECK_CUDA(cudaMemcpy(d_UIview_test, UIview_test, m*n*sizeof(float), cudaMemcpyHostToDevice));
	int *d_hamming_distance;
	CHECK_CUDA(cudaMalloc(&d_hamming_distance, n*m*sizeof(int)));
	get_hamming_distance(d_hamming_distance, d_B, d_D, r, m, n, d_UIview_test);
	CHECK_CUDA(cudaFree(d_B)); d_B = NULL;
	CHECK_CUDA(cudaFree(d_D)); d_D = NULL;

	int k = result_num;
	int *d_predict;
	CHECK_CUDA(cudaMalloc(&d_predict, k*m*sizeof(float)));
	get_min_k_predict(d_predict, k, m, d_hamming_distance, n);
	CHECK_CUDA(cudaFree(d_hamming_distance)); d_hamming_distance = NULL;

	float *d_measure;
	CHECK_CUDA(cudaMalloc(&d_measure, k*m*sizeof(float)));
	get_measure(d_measure, d_predict, k, m, n, d_UIview_test);
	CHECK_CUDA(cudaFree(d_UIview_test)); d_UIview_test = NULL;
	CHECK_CUDA(cudaFree(d_predict)); d_predict = NULL;


	clock_t start = clock();
	cublasHandle_t cublashandle;
	CHECK_CUDA_STATUS(cublasCreate(&cublashandle));
	float map_score = get_map(d_measure, k, m, threshold, cublashandle);
	CHECK_CUDA_STATUS(cublasDestroy(cublashandle));

	float ndcg_score = get_ndcg(d_measure, k, m, UIview_test, n);
	CHECK_CUDA(cudaFree(d_measure));
	printf("\tr: %d\tK: %d\tcost time:%.2fs\nmap: %f\tndcg: %f\n", r, result_num, 
		(double)(clock() - start) / CLOCKS_PER_SEC, map_score, ndcg_score);

}

int main(){

	cudaError_t cudaStatus = cudaSetDevice(0);
	if (cudaStatus != cudaSuccess) {
		fprintf(stderr, "cudaSetDevice failed!  Do you have a CUDA-capable GPU installed?");
		system("pause"); exit(1);
	}
	
	Params params;
	params.split_rate = 0.8f;
	params.maxS = 5;
	params.minS = 1;
	params.num_of_kmeans = 3;
	params.num_of_LAE_iters = 10;
	float *UIview_test = (float*)malloc(nUsers*nItems*sizeof(float));
	CHECK_PARAM(UIview_test != NULL, "allocate space for uiview_test failed.");

	std::stringstream ss;
	ss << nUsers << "_" << nItems << "_UIview_test.txt";
	read_test_data(UIview_test, nUsers, nItems, ss.str().c_str()); ss.str("");

	int rbits[5] = { 8, 16, 32, 64, 128 };
	for (int idx = 0; idx < 1; idx++){
		params.r = rbits[idx];
		printf("====================\n%d: r:%d\n", idx, params.r);

		int *B = (int *)malloc(params.r*nUsers*sizeof(float));
		CHECK_PARAM(B != NULL, "allocate space for B failed.");
		int *D = (int *)malloc(params.r*nItems*sizeof(float));
		CHECK_PARAM(D != NULL, "allocate space for D failed.");

		int nas[11] = { 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120 };
		int nnas[11] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
		float hypers[13] = { 0.01, 0.03, 0.06, 0.1, 0.3, 0.6, 1, 3, 6, 10, 30, 60, 100 };

		int ks[5] = { 5, 10, 15, 20, 25 };

		for (int p_id = 0; p_id < 5; p_id++){

			params.num_of_anchors = 20;
			params.num_of_nearest_anchors = 5;

			float hyper = 0.03;
			params.alpha = hyper;
			params.beta = hyper;
			params.rho1 = hyper;
			params.rho2 = hyper;
			//printf("====================\n%d: num_of_nearest_anchors:%d\n", idx, 
			//	params.num_of_nearest_anchors);

			std::string params_str;
			params_to_string(params, params_str);

			ss.str("");
			ss << nUsers << "_" << nItems << "_" << params.r << "_"
				<< params_str << "_" 
				<< "B_code.txt";
			read_from_file(B, params.r, nUsers, ss.str().c_str()); 

			ss.str("");
			ss << nUsers << "_" << nItems << "_" << params.r << "_"
				<< params_str << "_" 
				<< "D_code.txt";
			read_from_file(D, params.r, nItems, ss.str().c_str()); 


			int result_num = ks[p_id];
			float threshold = 3.5;
			evaluate(B, D, params.r, nUsers, nItems, UIview_test, result_num, threshold);


		}

		free(B);
		free(D);
	}
	free(UIview_test);

	system("pause");
	return 0;
}